"""
Sing-box内核管理服务
支持下载、安装、管理Sing-box内核
"""
import os
import sys
import platform
import requests
import zipfile
import tarfile
import subprocess
from pathlib import Path
from typing import Tuple, Optional
from api.utils.logger import get_logger

logger = get_logger("singbox_manager")


class SingBoxManager:
    """Sing-box内核管理器"""
    
    # Sing-box GitHub releases
    SINGBOX_RELEASES_URL = "https://api.github.com/repos/SagerNet/sing-box/releases/latest"
    SINGBOX_DOWNLOAD_BASE = "https://github.com/SagerNet/sing-box/releases/download"
    
    # 本地存储路径
    SINGBOX_DIR = Path("runtime/singbox")
    SINGBOX_EXECUTABLE = SINGBOX_DIR / "sing-box"
    
    @staticmethod
    def get_system_info() -> Tuple[str, str]:
        """
        获取系统信息
        Returns:
            (os_type, arch) 例如: ('linux', 'amd64')
        """
        system = platform.system().lower()
        machine = platform.machine().lower()
        
        # 操作系统映射
        os_map = {
            'linux': 'linux',
            'darwin': 'darwin',
            'windows': 'windows'
        }
        
        # 架构映射
        arch_map = {
            'x86_64': 'amd64',
            'amd64': 'amd64',
            'aarch64': 'arm64',
            'arm64': 'arm64',
            'armv7l': 'armv7',
            'i386': '386',
            'i686': '386'
        }
        
        os_type = os_map.get(system, 'linux')
        arch = arch_map.get(machine, 'amd64')
        
        return os_type, arch
    
    @staticmethod
    def check_singbox_installed() -> Tuple[bool, Optional[str]]:
        """
        检查Sing-box是否已安装
        Returns:
            (是否安装, 版本号)
        """
        if not SingBoxManager.SINGBOX_EXECUTABLE.exists():
            return False, None
        
        try:
            result = subprocess.run(
                [str(SingBoxManager.SINGBOX_EXECUTABLE), 'version'],
                capture_output=True,
                text=True,
                timeout=5
            )
            
            if result.returncode == 0:
                # 解析版本号
                # sing-box version 输出格式: sing-box version 1.8.0
                version_line = result.stdout.strip()
                if 'sing-box version' in version_line:
                    version = version_line.split('version')[-1].strip()
                    return True, version
            
            return False, None
            
        except Exception as e:
            logger.error(f"检查Sing-box版本失败: {e}")
            return False, None
    
    @staticmethod
    def get_latest_version() -> Tuple[bool, Optional[str], str]:
        """
        获取最新版本信息
        Returns:
            (是否成功, 版本号, 错误信息)
        """
        try:
            response = requests.get(SingBoxManager.SINGBOX_RELEASES_URL, timeout=10)
            response.raise_for_status()
            
            data = response.json()
            version = data.get('tag_name', '').lstrip('v')
            
            if version:
                return True, version, ""
            else:
                return False, None, "无法获取版本信息"
                
        except Exception as e:
            logger.error(f"获取最新版本失败: {e}")
            return False, None, f"获取版本失败: {str(e)}"
    
    @staticmethod
    def download_singbox(version: str = None, progress_callback=None) -> Tuple[bool, str]:
        """
        下载Sing-box内核
        
        Args:
            version: 版本号，不指定则下载最新版
            progress_callback: 进度回调函数 callback(downloaded, total)
            
        Returns:
            (是否成功, 消息)
        """
        try:
            # 获取最新版本
            if not version:
                success, version, error = SingBoxManager.get_latest_version()
                if not success:
                    return False, error
            
            # 获取系统信息
            os_type, arch = SingBoxManager.get_system_info()
            
            # 构建下载文件名
            # sing-box-1.8.0-linux-amd64.tar.gz
            if os_type == 'windows':
                filename = f"sing-box-{version}-windows-{arch}.zip"
            else:
                filename = f"sing-box-{version}-{os_type}-{arch}.tar.gz"
            
            download_url = f"{SingBoxManager.SINGBOX_DOWNLOAD_BASE}/v{version}/{filename}"
            
            logger.info(f"开始下载Sing-box {version}: {download_url}")
            
            # 创建目录
            SingBoxManager.SINGBOX_DIR.mkdir(parents=True, exist_ok=True)
            download_path = SingBoxManager.SINGBOX_DIR / filename
            
            # 下载文件
            response = requests.get(download_url, stream=True, timeout=300)
            response.raise_for_status()
            
            total_size = int(response.headers.get('content-length', 0))
            downloaded = 0
            
            with open(download_path, 'wb') as f:
                for chunk in response.iter_content(chunk_size=8192):
                    if chunk:
                        f.write(chunk)
                        downloaded += len(chunk)
                        
                        if progress_callback and total_size > 0:
                            progress_callback(downloaded, total_size)
            
            logger.info(f"下载完成: {download_path}")
            
            # 解压文件
            logger.info("正在解压...")
            SingBoxManager._extract_archive(download_path)
            
            # 删除压缩包
            download_path.unlink()
            
            # 设置可执行权限（Linux/macOS）
            if os_type in ['linux', 'darwin']:
                os.chmod(SingBoxManager.SINGBOX_EXECUTABLE, 0o755)
            
            logger.info("Sing-box安装完成")
            return True, f"Sing-box {version} 安装成功"
            
        except Exception as e:
            logger.error(f"下载Sing-box失败: {e}", exc_info=True)
            return False, f"下载失败: {str(e)}"
    
    @staticmethod
    def install_from_file(file_path: str) -> Tuple[bool, str]:
        """
        从本地文件安装Sing-box
        
        Args:
            file_path: 本地压缩文件路径（支持.zip或.tar.gz）
            
        Returns:
            (是否成功, 消息)
        """
        try:
            archive_path = Path(file_path)
            
            if not archive_path.exists():
                return False, f"文件不存在: {file_path}"
            
            if not (archive_path.suffix in ['.zip', '.gz'] or archive_path.name.endswith('.tar.gz')):
                return False, "仅支持ZIP或TAR.GZ格式文件"
            
            logger.info(f"开始从本地文件安装Sing-box: {file_path}")
            
            # 确保目录存在
            SingBoxManager.SINGBOX_DIR.mkdir(parents=True, exist_ok=True)
            
            # 解压文件
            logger.info("正在解压...")
            SingBoxManager._extract_archive(archive_path)
            
            # 删除压缩包
            archive_path.unlink()
            
            # 设置可执行权限（Linux/macOS）
            os_type, _ = SingBoxManager.get_system_info()
            if os_type in ['linux', 'darwin']:
                os.chmod(SingBoxManager.SINGBOX_EXECUTABLE, 0o755)
            
            # 验证安装
            installed, version = SingBoxManager.check_singbox_installed()
            if installed:
                logger.info(f"Sing-box安装成功，版本: {version}")
                return True, f"Sing-box {version} 安装成功"
            else:
                return False, "安装后验证失败，请检查文件是否正确"
            
        except Exception as e:
            logger.error(f"从文件安装Sing-box失败: {e}", exc_info=True)
            return False, f"安装失败: {str(e)}"
    
    @staticmethod
    def _extract_archive(archive_path: Path):
        """解压压缩文件"""
        extract_to = SingBoxManager.SINGBOX_DIR
        
        if archive_path.suffix == '.zip':
            with zipfile.ZipFile(archive_path, 'r') as zip_ref:
                zip_ref.extractall(extract_to)
        elif archive_path.suffix == '.gz' or archive_path.name.endswith('.tar.gz'):
            with tarfile.open(archive_path, 'r:*') as tar_ref:
                tar_ref.extractall(extract_to)
        else:
            raise ValueError(f"不支持的压缩格式: {archive_path.suffix}")
        
        # sing-box压缩包解压后通常在子目录中，需要移动到正确位置
        # 查找sing-box可执行文件
        for root, dirs, files in os.walk(extract_to):
            for file in files:
                if file == 'sing-box' or file == 'sing-box.exe':
                    source = Path(root) / file
                    if source != SingBoxManager.SINGBOX_EXECUTABLE:
                        # 移动到目标位置
                        SingBoxManager.SINGBOX_EXECUTABLE.parent.mkdir(parents=True, exist_ok=True)
                        source.rename(SingBoxManager.SINGBOX_EXECUTABLE)
                        logger.info(f"已移动可执行文件到: {SingBoxManager.SINGBOX_EXECUTABLE}")
                        break
    
    @staticmethod
    def remove_singbox() -> Tuple[bool, str]:
        """
        卸载Sing-box内核
        Returns:
            (是否成功, 消息)
        """
        try:
            if SingBoxManager.SINGBOX_DIR.exists():
                import shutil
                shutil.rmtree(SingBoxManager.SINGBOX_DIR)
                logger.info("Sing-box已卸载")
                return True, "Sing-box卸载成功"
            else:
                return True, "Sing-box未安装"
                
        except Exception as e:
            logger.error(f"卸载Sing-box失败: {e}")
            return False, f"卸载失败: {str(e)}"
    
    @staticmethod
    def get_singbox_path() -> Optional[str]:
        """获取Sing-box可执行文件路径"""
        if SingBoxManager.SINGBOX_EXECUTABLE.exists():
            return str(SingBoxManager.SINGBOX_EXECUTABLE.absolute())
        return None